/*
Copyright 2010 Bryce Alcock

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.com

 */
package com.ecocrypt.proxy.forwarder.remote;

import com.ecocrypt.proxy.forwarder.ClientServiceRequest;
import com.ecocrypt.proxy.forwarder.RemoteForwarderStub;
import com.ecocrypt.proxy.forwarder.gui.ForwardPanelTableModel;
import com.ecocrypt.proxy.forwarder.gui.ForwarderPanelTop;
import com.ecocrypt.proxy.forwarder.gui.RemoteForwarderView;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author bryce.alcock
 */
public class CommandConsole {

    public static void createForwardDestination(ForwarderDestination fd, CommandConsole context) throws IOException {

       RemoteForwarderView.getInstance().addFowarderPanelTop(fd, null);

	if (context == null) {
	    context = staticContext;
	    if (staticContext == null) {
		throw new IllegalStateException("Context must not be null for non-static context invokations");
	    }
	}
	RemoteRequestProcessingThread rrpt = context.new RemoteRequestProcessingThread(fd);
	rrpt.start();

    }

    private final HashMap<String, RemoteForwarderStub> forwarderStubs = new HashMap<String, RemoteForwarderStub>();

    private ForwarderDestination getForwarderDestination(String s) {
	ForwarderDestination fd = new ForwarderDestination();
	fd.setIpAddress(s.split(":")[0]);
	fd.setPort(Integer.parseInt(s.split(":")[1]));
	return fd;
    }
    static CommandConsole staticContext = null;

    public static void main(String args[]) {


	CommandConsole context = new CommandConsole();
	staticContext = context;
	ArrayList<ForwarderDestination> fd = new ArrayList<ForwarderDestination>();
	for (int i = 0; i < args.length; ++i) {
	    if (args[i].equals("-f")) {
		++i;
		fd.add(context.getForwarderDestination(args[i]));
	    }
	}

	try {
	    for (ForwarderDestination forwarderDestination : fd) {
		createForwardDestination(forwarderDestination, context);
	    }
	} catch (UnknownHostException ex) {
	    Logger.getLogger(CommandConsole.class.getName()).log(Level.SEVERE, null, ex);
	} catch (IOException ex) {
	    Logger.getLogger(CommandConsole.class.getName()).log(Level.SEVERE, null, ex);
	}

	//RemoteForwarderStub rfs = new RemoteForwarderStub("-1:Testing:-2");
	//ForwardPanelTableModel fptm = new ForwardPanelTableModel(rfs);
	//ForwarderPanelTop fpt = new ForwarderPanelTop(fptm, rfs);
	//ForwarderView.getInstance().addFowarderPanelTop(fpt);
	RemoteForwarderView.getInstance().setSize(500, 699);

	RemoteForwarderView.getInstance().setVisible(true);

    }

    private void handleObject(Object o, ForwarderDestination fd) {
	if (o instanceof RemoteForwarderStub) {
	    System.out.println("Adding : " + o.toString());

	    RemoteForwarderStub stub = (RemoteForwarderStub) o;
	    forwarderStubs.put(stub.toString(), stub);
	    ForwardPanelTableModel fptm = new ForwardPanelTableModel(stub);
	    ForwarderPanelTop fpt = new ForwarderPanelTop(fptm, stub);
	    fd.setLastByteTime(System.currentTimeMillis());
	    RemoteForwarderView.getInstance().addFowarderPanelTop(fd, fpt);
	} else if (o instanceof Command) {
	    Command c = (Command) o;
	    if (c.getCommand() != null && c.getCommand().equals(ForwarderDynamicStateChangedPayload.COMMAND)) {
		//  The the forwarder.

		//  Update the payload.
		ForwarderDynamicStateChangedPayload payload = (ForwarderDynamicStateChangedPayload) c.getPayload();
		payload.printOut();
		RemoteForwarderStub stub = forwarderStubs.get(payload.forwarderName);
		System.out.println("*** Updating Stub with Hash: " + stub.hashCode());
		stub.setDynamicState(payload);

	    } else if (Command.REMOVE_SERVICE_REQUEST.equals(c.getCommand())) {
		ClientServiceRequest csr = (ClientServiceRequest) c.getPayload();
		System.out.println("Command: "+Command.REMOVE_SERVICE_REQUEST);
		RemoteForwarderStub stub = forwarderStubs.get(csr.getForwarderName());
		stub.removeServiceRequest(csr);
	    } else if (Command.ADD_SERVICE_REQUEST.equals(c.getCommand())) {
		System.out.println("Command: "+Command.REMOVE_SERVICE_REQUEST);
		ClientServiceRequest csr = (ClientServiceRequest) c.getPayload();
		RemoteForwarderStub stub = forwarderStubs.get(csr.getForwarderName());
		stub.addServiceRequest(csr);

	    } else if (Command.UPDATE_SERVICE_REQUEST.equals(c.getCommand())) {
		ClientServiceRequest csr = (ClientServiceRequest) c.getPayload();
		RemoteForwarderStub stub = forwarderStubs.get(csr.getForwarderName());
		stub.connectionDataChanged(csr);

	    }
	}
    }

    class RemoteRequestProcessingThread extends Thread {

	ForwarderDestination fd;
	ObjectInputStream ois;
	ObjectOutputStream oos;
	Socket sock;
	boolean alive = true;
        int globalResetCount =0;

        private static final String STATUS_RESTTING_STRING     = "Resetting %d";
        private static final String STATUS_CONNECTED_STRING     = "CONNECTED %d";
        private static final String STATUS_CONNECT_WAIT_STRING = "Connect-WAIT %d sec, %d";
        private static final String STATUS_CONNECT_EXCEPTION_STRING       = "Exception %d";

	RemoteRequestProcessingThread(ForwarderDestination lfd) {
	    fd = lfd;
	}

	private void resetSockets() {
            ++globalResetCount;
            fd.setStatus(String.format(STATUS_RESTTING_STRING, globalResetCount));
	    boolean success = false;
	    int  attemptCount =0;
	    if(sock!=null){
		try{
		    if(!sock.isClosed()){
			sock.close();
		    }
		    sock=null;
		}catch(Exception ex){
			Logger.getLogger(CommandConsole.class.getName()).log(Level.SEVERE, null, ex);
		}
	    }
	    while (!success && alive) {
		try {
		    try {
			sock = new Socket(fd.getIpAddress(), fd.getPort());
			InputStream is = sock.getInputStream();
			ois = new ObjectInputStream(is);
			oos = new ObjectOutputStream(sock.getOutputStream());
			success = true;
                        fd.setStatus(String.format(STATUS_CONNECTED_STRING, globalResetCount));
                        return;

		    } catch (UnknownHostException ex) {
                        fd.setStatus(String.format(STATUS_CONNECT_EXCEPTION_STRING, globalResetCount));
			Logger.getLogger(CommandConsole.class.getName()).log(Level.SEVERE, null, ex);
		    } catch (IOException ex) {
                        fd.setStatus(String.format(STATUS_CONNECT_EXCEPTION_STRING, globalResetCount));
			success = false;
			Logger.getLogger(CommandConsole.class.getName()).log(Level.SEVERE, null, ex);
		    }
		    //  Sleep not more than 2 minutes and then retry...
                    int delay = Math.min(attemptCount++ * 2000, 120 * 1000);
                    fd.setStatus(String.format(STATUS_CONNECT_WAIT_STRING,delay, globalResetCount));
                    Thread.sleep(delay);
		} catch (InterruptedException ex) {
                    fd.setStatus(String.format(STATUS_CONNECT_EXCEPTION_STRING, globalResetCount));
		    Logger.getLogger(CommandConsole.class.getName()).log(Level.SEVERE, null, ex);
		}
	    }
	}

	@Override
	public void run() {
	    resetSockets();
	    while (alive) {
		try {
		    Object o = ois.readObject();
		    //System.out.println("object in: " + String.valueOf(o));
		    handleObject(o, fd);
		} catch (IOException ex) {
		    resetSockets();
		    Logger.getLogger(CommandConsole.class.getName()).log(Level.SEVERE, null, ex);
		} catch (ClassNotFoundException ex) {
                    try {
                        fd.setStatus(String.format(STATUS_CONNECT_EXCEPTION_STRING, globalResetCount));
                        int delay = Math.min(globalResetCount * 2000, 120 * 1000);
                        fd.setStatus(String.format(STATUS_CONNECT_WAIT_STRING, delay, globalResetCount));
                        Thread.sleep(delay);
                        Logger.getLogger(CommandConsole.class.getName()).log(Level.SEVERE, null, ex);
                    } catch (InterruptedException ex1) {
                        Logger.getLogger(CommandConsole.class.getName()).log(Level.SEVERE, null, ex1);
                    }
		}
	    }
	}
    }
}
